using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.SignalR.Client;
using MultiAgentsShared;

namespace MultiAgentsClient.Service.SignalRService;

public class CustomHttpMessageHandler : HttpClientHandler
{
    public CustomHttpMessageHandler()
    {
        // TODO: delete
        ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
    }
}

public abstract class SignalRClientServiceBase
{
    protected HubConnection? hubConnection;
    protected readonly IEventBus eventBus;
    protected readonly ServerIPConfig serverIPConfig;
    protected readonly Port ports;

    protected static string SelectServerName { get; set; } = "";
    protected string? ServerUrl
    {
        get
        {
            int index = serverIPConfig.ServerName.FindIndex(name => name == SelectServerName);
            if (index == -1)
            {
                return null ;
            }

            string ip = serverIPConfig.ServerIp[index];
#if LOCALDEV
            return ip + ":" + ports.LocalDev;
#elif DEVELOP
            return ip + ":" + ports.Develop;
#elif RELEASE
            return ip + ":" + ports.Release;
#endif
        }
    }

    private readonly string targetHubAddress;

    // Whenever server is trying to proactively send msg to client,
    // when user login, client would make a request to server using this command,
    // which is to pass connection&sessionId relationship to server.
    public record SessionStartCommand(string SessionId, int UserId);
    public record ConnectServerCommand(string ServerName);

    public SignalRClientServiceBase(string targetHubAddress)
    {
        eventBus = EventBus.Instance;
        this.targetHubAddress = targetHubAddress;

        eventBus.Subscribe<ConnectServerCommand>(ConnectServer);

        serverIPConfig = AppConfigService.GetSection<ServerIPConfig>("ServerIPConfig");
        ports = AppConfigService.GetSection<Port>("Port");

        _ = KeepConnection();
    }

    private void ConnectServer(ConnectServerCommand cmd)
    {
        SelectServerName = cmd.ServerName;
    }

    protected void EstablishConnection()
    {
        if (ServerUrl != null)
        {
            hubConnection = new HubConnectionBuilder().WithUrl($"{ServerUrl}/{targetHubAddress}", options =>
            {
                options.HttpMessageHandlerFactory = _ => new CustomHttpMessageHandler(); //TODO: delete
            }).Build();
        }
    }

    protected abstract void HubMethodRegistration();

    public async Task KeepConnection()
    {
        while (true)
        {
            if (hubConnection == null)
            {
                EstablishConnection();
                await Task.Delay(100);
            }
            else if (hubConnection.State == HubConnectionState.Disconnected)
            {
                EstablishConnection();
                HubMethodRegistration();
                Debug.LogLine("Connecting...");
                try
                {
                    await hubConnection.StartAsync();
                    _ = OnConnectCompleted();
                    Debug.LogLine("Connected!");
                }
                catch
                {
                    Debug.LogLine("Connect failed.");
                }
            }
            else
            {
                await Task.Delay(1000);
            }
        }
    }

    protected virtual async Task OnConnectCompleted() { }

    public async Task SendSignalRRequest(string requestName, Dictionary<string, object> requestParams)
    {
        try
        {
            for (int i = 0; i < 100; i++)
            {
                if (hubConnection == null || hubConnection.State != HubConnectionState.Connected)
                {
                    await Task.Delay(150);
                    if (i == 33)
                    {
                        Debug.LogWindow("网络连接错误，请检查网络设置并重新登陆，或联系管理员。");
                        return;
                    }
                    continue;
                }
            }

            if (Encoding.UTF8.GetByteCount(JsonSerializer.Serialize(requestParams)) > 20 * 1024 * 1024) // 20 MB
            {
                Debug.LogWarning("Request size is too large.");
                return;
            }

            await hubConnection.InvokeAsync(requestName, requestParams);
        }
        catch (Exception ex)
        {
            Debug.LogError($"An error occurred: {ex.Message}");
        }
    }
}

public static class SignalRHubExtension
{
    public static T? GetData<T>(this Dictionary<string, object?> dictionary, string key)
    {
        if (!dictionary.TryGetValue(key, out object? obj) || obj is not JsonElement jsonElement)
        {
            return default;
        }

        if (typeof(T).IsEnum)
        {
            if (jsonElement.ValueKind == JsonValueKind.String)
            {
                string enumString = jsonElement.GetString() ?? throw new InvalidOperationException($"Value for key '{key}' is null.");
                return (T)Enum.Parse(typeof(T), enumString);
            }
            if (jsonElement.ValueKind == JsonValueKind.Number)
            {
                int enumInt = jsonElement.GetInt32();
                return (T)Enum.ToObject(typeof(T), enumInt);
            }
            throw new InvalidOperationException($"Unsupported JSON value kind for enum conversion: {jsonElement.ValueKind}");
        }

        return jsonElement.ValueKind switch
        {
            JsonValueKind.String when typeof(T) == typeof(string) => (T)(object)(jsonElement.GetString() ?? ""),
            JsonValueKind.Number when typeof(T) == typeof(int) => (T)(object)jsonElement.GetInt32(),
            JsonValueKind.Number when typeof(T) == typeof(double) => (T)(object)jsonElement.GetDouble(),
            JsonValueKind.Number when typeof(T) == typeof(float) => (T)(object)((float)jsonElement.GetDouble()),
            JsonValueKind.True or JsonValueKind.False when typeof(T) == typeof(bool) => (T)(object)jsonElement.GetBoolean(),
            JsonValueKind.String when typeof(T) == typeof(DateTime) && DateTime.TryParse(jsonElement.GetString(), out var dateTimeVal) => (T)(object)dateTimeVal,
            JsonValueKind.Object or JsonValueKind.Array =>
                JsonSerializer.Deserialize<T>(jsonElement.GetRawText()) ?? throw new InvalidOperationException($"Deserialization resulted in null for type: {typeof(T).Name}"),
            _ => throw new InvalidOperationException($"Unsupported conversion type: {typeof(T).Name}")
        };
    }
}
